#include "StdAfx.h"
#include <MyControls/Navigator.h>
#include <MyControls/Timeline.h>
#include <MyControls/Resource.h>
#include <MyControls/App.h>
#include <MyFC/Utility.h>
#include <cmath>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace My {

// Navigator::Control

Navigator::Control::Control(Navigator & n)
	: m_Navigator(n), m_Timeline(n.m_Timeline)
{
	m_Navigator.Add(this);
}

bool Navigator::Control::Visible() const
{
	return !m_Timeline.IsEmpty();
}

// LeftBracket

CRect Navigator::LeftBracket::Bounds() const
{
	return AlignRect(
		m_Navigator.m_NavVisibleLeft.Bounds(),
		CPoint(m_Navigator.TimeToX(m_Timeline.GetTimeOffset()), 0),
		ALIGN_HRIGHT|ALIGN_VTOP);
}

String Navigator::LeftBracket::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_VISIBLE_AREA_START);
}

bool Navigator::LeftBracket::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Navigator.TimeToX(m_Timeline.GetTimeOffset());
	m_RightViewBound = m_Timeline.GetTimeOffset() + m_Timeline.GetTimePage();
	m_MinValue = m_Navigator.m_Timeline.GetGap();
	m_MaxValue = m_Navigator.TimeToX(m_RightViewBound - m_Timeline.GetXPage() / m_Timeline.GetMaxTimeScale());
	return true;
}

void Navigator::LeftBracket::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	if (x < m_MinValue)
		x = m_MinValue;
	if (x > m_MaxValue)
		x = m_MaxValue;
	double t = m_Navigator.XToTime(x);
	double s = m_Timeline.GetMaxTimeScale();
	if (m_RightViewBound > t)
		s = double(m_Timeline.GetXPage()) / (m_RightViewBound - t);
	m_Timeline.SetTimeScale(s);
	m_Timeline.SetTimeOffset(m_RightViewBound - m_Timeline.GetTimePage());
	m_Timeline.Update();
}

// RightBracket

CRect Navigator::RightBracket::Bounds() const
{
	return AlignRect(
		m_Navigator.m_NavVisibleRight.Bounds(),
		CPoint(m_Navigator.TimeToX(m_Timeline.GetTimeOffset() + m_Timeline.GetTimePage()), 0),
		ALIGN_HLEFT|ALIGN_VTOP);
}

String Navigator::RightBracket::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_VISIBLE_AREA_END);
}

bool Navigator::RightBracket::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Navigator.TimeToX(m_Timeline.GetTimeOffset() + m_Timeline.GetTimePage());
	m_LeftViewBound = m_Timeline.GetTimeOffset();
	m_MinValue = m_Navigator.TimeToX(m_LeftViewBound + m_Timeline.GetXPage() / m_Timeline.GetMaxTimeScale());
	m_MaxValue = m_Navigator.Width() - m_Navigator.m_Timeline.GetGap();
	return true;
}

void Navigator::RightBracket::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	if (x < m_MinValue)
		x = m_MinValue;
	if (x > m_MaxValue)
		x = m_MaxValue;
	double dt = m_Navigator.XToTime(x) - m_LeftViewBound;
	double s = m_Timeline.GetMaxTimeScale();
	if (dt > 0)
		s = double(m_Timeline.GetXPage()) / dt;
	m_Timeline.SetTimeScale(s);
	m_Timeline.SetTimeOffset(m_LeftViewBound);
	m_Timeline.Update();
}

// Bar

CRect Navigator::Bar::Bounds() const
{
	if (m_Timeline.IsEmpty())
		return false;
	return CRect(
		m_Navigator.TimeToX(m_Timeline.GetTimeOffset()), 
		0, 
		m_Navigator.TimeToX(m_Timeline.GetTimeOffset() + m_Timeline.GetTimePage()), 
		m_Navigator.Height()
	);
}

String Navigator::Bar::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_VISIBLE_AREA);
}

bool Navigator::Bar::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Navigator.TimeToX(m_Timeline.GetTimeOffset());
	return true;
}

void Navigator::Bar::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	m_Timeline.SetTimeOffset(m_Navigator.XToTime(x));
	m_Timeline.Update();
}

// Thumb

CRect Navigator::Thumb::Bounds() const
{
	return AlignRect(
		m_Navigator.m_NavThumb.Bounds(),
		CPoint(m_Navigator.TimeToX(m_Timeline.GetCurrentFrame()), 0),
		ALIGN_HCENTER|ALIGN_VTOP);
}

String Navigator::Thumb::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_CURRENT_FRAME);
}

bool Navigator::Thumb::HandleDragStart(unsigned int flags, const CPoint & p0, const CPoint & p)
{
	if (m_Timeline.IsEmpty())
		return false;
	m_StartPos = ClientToParent(p0).x;
	m_StartValue = m_Navigator.TimeToX(m_Timeline.GetCurrentFrame());
	return true;
}

void Navigator::Thumb::HandleDragEnd(unsigned int flags, const CPoint & p)
{
	m_Timeline.UpdateCurrentFrame();
}

void Navigator::Thumb::HandleDragging(unsigned int flags, const CPoint & p)
{
	int x = m_StartValue + ClientToParent(p).x - m_StartPos;
	m_Timeline.SetCurrentFrame(round(m_Navigator.XToTime(x)));
}

// Navigator

Navigator::Navigator(Timeline & t) : 
	m_Timeline(t),
	m_Bar(* this),
	m_LeftBracket(* this),
	m_RightBracket(* this),
	m_Thumb(* this)
{
	m_PaintOutside = true;
	m_NavThumb.Load("NAVTHUMB", "NAVTHUMBMASK");
	m_NavWALeft.Load("NAVWALEFT", "NAVWALEFTMASK");
	m_NavWARight.Load("NAVWARIGHT", "NAVWARIGHTMASK");
	m_NavVisibleLeft.Load("NAVVISIBLELEFT", "NAVVISIBLELEFTMASK");
	m_NavVisibleRight.Load("NAVVISIBLERIGHT", "NAVVISIBLERIGHTMASK");
}

String Navigator::Hint() const
{
	return GetApp().LoadString(IDS_TIMELINE_NAVIGATOR);
}

int Navigator::TimeToX(double time)
{
	assert(!m_Timeline.IsEmpty());
	return m_Timeline.GetGap() 
		+ Round((Width() - 2 * m_Timeline.GetGap()) 
			* (time - m_Timeline.GetSequence()->GetStart()) 
			/ m_Timeline.GetTimeRange());
}

double Navigator::XToTime(int x)
{
	assert(!m_Timeline.IsEmpty());
	return m_Timeline.GetSequence()->GetStart() 
		+ double(x - m_Timeline.GetGap()) 
			* m_Timeline.GetTimeRange() 
			/ (Width() - 2 * m_Timeline.GetGap());
}

void Navigator::ProcessEvent(Event & e)
{
	if (e.Is<Sequence::PreviewFrameChange>())
		return;
	if (e.Is<Sequence::Event>() || e.Is<Timeline::Event>())
		Invalidate();
	else
		Container::ProcessEvent(e);
}

void Navigator::HandlePaint(CDC * pDC, const CRect & rect)
{
	SaveObjects save(pDC);
	CRect rClient = ClientRect();

	CRect rClip(rClient);
	rClip.bottom++;
	ClipRect clip(this, pDC, rClip);

	CBrush bFace; bFace.CreateSysColorBrush(COLOR_BTNFACE);
	CBrush bLight; bLight.CreateSysColorBrush(COLOR_BTNHILIGHT);
	CBrush bHalfShadow(My::MeanSysColor(COLOR_BTNFACE, COLOR_BTNSHADOW));
	CBrush bHalfLight(My::MeanSysColor(COLOR_BTNFACE, COLOR_BTNHILIGHT));

	CPen pBlack; pBlack.CreateStockObject(BLACK_PEN);
	CPen pLight(PS_SOLID, 1, GetSysColor(COLOR_BTNHILIGHT));
	CPen pShadow(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));

	CRect rRange = rClient;
	rRange.left += m_Timeline.GetGap();
	rRange.right -= m_Timeline.GetGap();
	pDC->FillRect(&rRange, &bHalfShadow);

	if (m_Timeline.IsEmpty())
		return;

	CRect rVisible(rClient);
	rVisible.left = rClient.left + TimeToX(m_Timeline.GetTimeOffset());
	rVisible.right = rClient.left + TimeToX(m_Timeline.GetTimeOffset() + m_Timeline.GetTimePage());
	pDC->FillRect(&rVisible, &bLight);

	CRect rWorkarea(rClient);
	Span workArea = m_Timeline.GetWorkArea();
	rWorkarea.left = rClient.left + TimeToX(workArea.first);
	rWorkarea.right = rClient.left + TimeToX(workArea.last);
	rWorkarea.top += 4;
	pDC->FillRect(&rWorkarea, &bHalfLight);

	pDC->SelectObject(&pBlack);
	pDC->MoveTo(rWorkarea.left, rWorkarea.top);
	pDC->LineTo(rWorkarea.right, rWorkarea.top);
	m_NavWALeft.Draw(pDC, rWorkarea.left, rWorkarea.top, ALIGN_HRIGHT|ALIGN_VTOP);
	m_NavWARight.Draw(pDC, rWorkarea.right, rWorkarea.top, ALIGN_HLEFT|ALIGN_VTOP);

	m_NavVisibleLeft.Draw(pDC, rVisible.left - m_NavVisibleLeft.Width(), rVisible.top);
	m_NavVisibleRight.Draw(pDC, rVisible.right, rVisible.top);

	m_NavThumb.Draw(pDC, rClient.left + TimeToX(m_Timeline.GetCurrentFrame()), rClient.top, ALIGN_HCENTER);
}

} // My
